/*
 *  ZKMORHP_Control.cpp
 *  Cushion
 *
 *  Created by Chandrasekhar Ramakrishnan on 28.02.08.
 *  Copyright 2008 Illposed Software. All rights reserved.
 *
 */

#include "ZKMORHP_Control.h"

//  Local Includes
#include "ZKMORHP_Device.h"
#include "ZKMORHP_PlugIn.h"

//  PublicUtility Includes
#include "CACFArray.h"
#include "CACFDictionary.h"
#include "CACFNumber.h"
#include "CACFString.h"
#include "CADebugMacros.h"
#include "CAException.h"

//==================================================================================================
//	ZKMORHP_LevelControl
//==================================================================================================

ZKMORHP_LevelControl::ZKMORHP_LevelControl(AudioObjectID inObjectID, AudioClassID inClassID, AudioObjectPropertyScope inDevicePropertyScope, AudioObjectPropertyElement inDevicePropertyElement, ZKMORHP_PlugIn* inPlugIn, ZKMORHP_Device* inOwningDevice)
:
	HP_LevelControl(inObjectID, inClassID, inPlugIn, inOwningDevice),
	mDevicePropertyScope(inDevicePropertyScope),
	mDevicePropertyElement(inDevicePropertyElement),
	mVolumeCurve(),
	mCurrentRawValue(0)
{
}

ZKMORHP_LevelControl::~ZKMORHP_LevelControl()
{
}

void	ZKMORHP_LevelControl::Initialize()
{
	//	cache the info about the control
	SInt32 theMinRaw = 0;
	SInt32 theMaxRaw = 1024;
	Float32 theMinDB = -90;
	Float32 theMaxDB = 0;
	
	//	set up the volume curve
	mVolumeCurve.ResetRange();
	mVolumeCurve.AddRange(theMinRaw, theMaxRaw, theMinDB, theMaxDB);
	
	//	cache the raw value
	CacheRawValue();
}

void	ZKMORHP_LevelControl::Teardown()
{
}

AudioObjectPropertyScope	ZKMORHP_LevelControl::GetPropertyScope() const
{
	return mDevicePropertyScope;
}

AudioObjectPropertyElement	ZKMORHP_LevelControl::GetPropertyElement() const
{
	return mDevicePropertyElement;
}

Float32	ZKMORHP_LevelControl::GetMinimumDBValue() const
{
	return mVolumeCurve.GetMinimumDB();
}

Float32	ZKMORHP_LevelControl::GetMaximumDBValue() const
{
	return mVolumeCurve.GetMaximumDB();
}

Float32	ZKMORHP_LevelControl::GetDBValue() const
{
	SInt32 theRawValue = GetRawValue();
	Float32 thDBValue = mVolumeCurve.ConvertRawToDB(theRawValue);
	return thDBValue;
}

void	ZKMORHP_LevelControl::SetDBValue(Float32 inDBValue)
{
	SInt32 theNewRawValue = mVolumeCurve.ConvertDBToRaw(inDBValue);
	SetRawValue(theNewRawValue);
}

Float32	ZKMORHP_LevelControl::GetScalarValue() const
{
	SInt32 theRawValue = GetRawValue();
	Float32 theScalarValue = mVolumeCurve.ConvertRawToScalar(theRawValue);
	return theScalarValue;
}

void	ZKMORHP_LevelControl::SetScalarValue(Float32 inScalarValue)
{
	SInt32 theNewRawValue = mVolumeCurve.ConvertScalarToRaw(inScalarValue);
	SetRawValue(theNewRawValue);
}

Float32	ZKMORHP_LevelControl::ConverScalarValueToDBValue(Float32 inScalarValue) const
{
	Float32 theDBValue = mVolumeCurve.ConvertScalarToDB(inScalarValue);
	return theDBValue;
}

Float32	ZKMORHP_LevelControl::ConverDBValueToScalarValue(Float32 inDBValue) const
{
	Float32 theScalarValue = mVolumeCurve.ConvertDBToScalar(inDBValue);
	return theScalarValue;
}

SInt32	ZKMORHP_LevelControl::GetRawValue() const
{
	//	Always get the value from the hardware and cache it in mCurrentRawValue. Note that if
	//	getting the value from the hardware fails for any reason, we just return mCurrentRawValue.
	//	We always just return mCurrentRawValue here because there is no hardware to talk to.
	return mCurrentRawValue;
}

void	ZKMORHP_LevelControl::SetRawValue(SInt32 inRawValue)
{
	//	Set the value in hardware. Note that mCurrentRawValue should be updated only if setting the
	//	hardware value is synchronous. Otherwise, mCurrentRawValue will be updated when the hardware
	//	notifies us that the value of the control changed. Here, we just directly set
	//	mCurrentRawValue because there is no hardware.
	if(inRawValue != mCurrentRawValue)
	{
		mCurrentRawValue = inRawValue;
	
		//	we also have to send the change notification
		ValueChanged();
	}
}

void	ZKMORHP_LevelControl::CacheRawValue()
{
	//	Set mCurrentRawValue to the value of the hardware. We do nothing here because there is no
	//	hardware.
}

//==================================================================================================
//	ZKMORHP_BooleanControl
//==================================================================================================

ZKMORHP_BooleanControl::ZKMORHP_BooleanControl(AudioObjectID inObjectID, AudioClassID inClassID, AudioObjectPropertyScope inDevicePropertyScope, AudioObjectPropertyElement inDevicePropertyElement, ZKMORHP_PlugIn* inPlugIn, ZKMORHP_Device* inOwningDevice)
:
	HP_BooleanControl(inObjectID, inClassID, inPlugIn, inOwningDevice),
	mDevicePropertyScope(inDevicePropertyScope),
	mDevicePropertyElement(inDevicePropertyElement),
	mCurrentValue(false)
{
}

ZKMORHP_BooleanControl::~ZKMORHP_BooleanControl()
{
}

void	ZKMORHP_BooleanControl::Initialize()
{
	//	cache the value
	CacheValue();
}

void	ZKMORHP_BooleanControl::Teardown()
{
}

AudioObjectPropertyScope	ZKMORHP_BooleanControl::GetPropertyScope() const
{
	return mDevicePropertyScope;
}

AudioObjectPropertyElement	ZKMORHP_BooleanControl::GetPropertyElement() const
{
	return mDevicePropertyElement;
}

bool	ZKMORHP_BooleanControl::GetValue() const
{
	//	Always get the value from the hardware and cache it in mCurrentValue. Note that if
	//	getting the value from the hardware fails for any reason, we just return mCurrentValue.
	//	We always just return mCurrentValue here because there is no hardware to talk to.
	return mCurrentValue;
}

void	ZKMORHP_BooleanControl::SetValue(bool inValue)
{
	//	Set the value in hardware. Note that mCurrentValue should be updated only if setting the
	//	hardware value is synchronous. Otherwise, mCurrentValue will be updated when the hardware
	//	notifies us that the value of the control changed. Here, we just directly set
	//	mCurrentValue because there is no hardware.
	if(inValue != mCurrentValue)
	{
		mCurrentValue = inValue;
	
		//	we also have to send the change notification
		ValueChanged();
	}
}

void	ZKMORHP_BooleanControl::CacheValue()
{
	//	Set mCurrentValue to the value of the hardware. We do nothing here because there is no hardware.
}

//==================================================================================================
//	ZKMORHP_SelectorControl
//==================================================================================================

ZKMORHP_SelectorControl::ZKMORHP_SelectorControl(AudioObjectID inObjectID, AudioClassID inClassID, AudioObjectPropertyScope inDevicePropertyScope, AudioObjectPropertyElement inDevicePropertyElement, ZKMORHP_PlugIn* inPlugIn, ZKMORHP_Device* inOwningDevice)
:
	HP_SelectorControl(inObjectID, inClassID, inPlugIn, inOwningDevice),
	mDevicePropertyScope(inDevicePropertyScope),
	mDevicePropertyElement(inDevicePropertyElement),
	mSelectorMap(),
	mCurrentItemID(0)
{
}

ZKMORHP_SelectorControl::~ZKMORHP_SelectorControl()
{
}

void	ZKMORHP_SelectorControl::Initialize()
{
	//	clear the current items
	mSelectorMap.clear();
	
	//	Insert items into mSelectorMap for all the items in this control. Here, we just stick in a
	//	few fake items.
	for(UInt32 theItemIndex = 0; theItemIndex < 4; ++theItemIndex)
	{
		//	make a name for the item
		CACFString theName(CFStringCreateWithFormat(NULL, NULL, CFSTR("Item %u"), theItemIndex));
		
		//	insert it into the map, using the item index as the item ID
		mSelectorMap.insert(SelectorMap::value_type(theItemIndex, SelectorItem(theName.CopyCFString(), 0)));
	}
	
	//	cache the current item ID
	CacheCurrentItemID();
}

void	ZKMORHP_SelectorControl::Teardown()
{
	mSelectorMap.clear();
}

AudioObjectPropertyScope	ZKMORHP_SelectorControl::GetPropertyScope() const
{
	return mDevicePropertyScope;
}

AudioObjectPropertyElement	ZKMORHP_SelectorControl::GetPropertyElement() const
{
	return mDevicePropertyElement;
}

UInt32	ZKMORHP_SelectorControl::GetNumberItems() const
{
	return mSelectorMap.size();
}

UInt32	ZKMORHP_SelectorControl::GetCurrentItemID() const
{
	//	Always get the value from the hardware and cache it in mCurrentItemID. Note that if
	//	getting the value from the hardware fails for any reason, we just return mCurrentItemID.
	//	We always just return mCurrentItemID here because there is no hardware to talk to.
	return mCurrentItemID;
}

UInt32	ZKMORHP_SelectorControl::GetCurrentItemIndex() const
{
	UInt32 theItemID = GetCurrentItemID();
	return GetItemIndexForID(theItemID);
}

void	ZKMORHP_SelectorControl::SetCurrentItemByID(UInt32 inItemID)
{
	//	Set the value in hardware. Note that mCurrentItemID should be updated only if setting the
	//	hardware value is synchronous. Otherwise, mCurrentItemID will be updated when the hardware
	//	notifies us that the value of the control changed. Here, we just directly set
	//	mCurrentItemID because there is no hardware.
	if(inItemID != mCurrentItemID)
	{
		mCurrentItemID = inItemID;
	
		//	we also have to send the change notification
		ValueChanged();
	}
}

void	ZKMORHP_SelectorControl::SetCurrentItemByIndex(UInt32 inItemIndex)
{
	UInt32 theItemID = GetItemIDForIndex(inItemIndex);
	SetCurrentItemByID(theItemID);
}

UInt32	ZKMORHP_SelectorControl::GetItemIDForIndex(UInt32 inItemIndex) const
{
	ThrowIf(inItemIndex >= mSelectorMap.size(), CAException(kAudioHardwareIllegalOperationError), "ZKMORHP_SelectorControl::GetItemIDForIndex: index out of range");
	SelectorMap::const_iterator theIterator = mSelectorMap.begin();
	std::advance(theIterator, inItemIndex);
	return theIterator->first;
}

UInt32	ZKMORHP_SelectorControl::GetItemIndexForID(UInt32 inItemID) const
{
	UInt32 theIndex = 0;
	bool wasFound = false;
	SelectorMap::const_iterator theIterator = mSelectorMap.begin();
	while(!wasFound && (theIterator != mSelectorMap.end()))
	{
		if(theIterator->first == inItemID)
		{
			wasFound = true;
		}
		else
		{
			++theIndex;
			std::advance(theIterator, 1);
		}
	}
	ThrowIf(!wasFound, CAException(kAudioHardwareIllegalOperationError), "ZKMORHP_SelectorControl::GetItemIndexForID: ID not in selector map");
	return theIndex;
}

CFStringRef	ZKMORHP_SelectorControl::CopyItemNameByID(UInt32 inItemID) const
{
	SelectorMap::const_iterator theIterator = mSelectorMap.find(inItemID);
	ThrowIf(theIterator == mSelectorMap.end(), CAException(kAudioHardwareIllegalOperationError), "ZKMORHP_SelectorControl::CopyItemNameByID: ID not in selector map");
	
	return (CFStringRef)CFRetain(theIterator->second.mItemName);
}

CFStringRef	ZKMORHP_SelectorControl::CopyItemNameByIndex(UInt32 inItemIndex) const
{
	CFStringRef theAnswer = NULL;
	
	if(inItemIndex < mSelectorMap.size())
	{
		SelectorMap::const_iterator theIterator = mSelectorMap.begin();
		std::advance(theIterator, inItemIndex);
		ThrowIf(theIterator == mSelectorMap.end(), CAException(kAudioHardwareIllegalOperationError), "ZKMORHP_SelectorControl::CopyItemNameByIndex: index out of range");
		
		theAnswer = (CFStringRef)CFRetain(theIterator->second.mItemName);
	}
		
	return theAnswer;
}

CFStringRef	ZKMORHP_SelectorControl::CopyItemNameByIDWithoutLocalizing(UInt32 inItemID) const
{
	return CopyItemNameByID(inItemID);
}

CFStringRef	ZKMORHP_SelectorControl::CopyItemNameByIndexWithoutLocalizing(UInt32 inItemIndex) const
{
	return CopyItemNameByIndex(inItemIndex);
}

UInt32	ZKMORHP_SelectorControl::GetItemKindByID(UInt32 inItemID) const
{
	SelectorMap::const_iterator theIterator = mSelectorMap.find(inItemID);
	ThrowIf(theIterator == mSelectorMap.end(), CAException(kAudioHardwareIllegalOperationError), "ZKMORHP_SelectorControl::GetItemKindByID: ID not in selector map");
	
	return theIterator->second.mItemKind;
}

UInt32	ZKMORHP_SelectorControl::GetItemKindByIndex(UInt32 inItemIndex) const
{
	UInt32 theAnswer = 0;
	
	if(inItemIndex < mSelectorMap.size())
	{
		SelectorMap::const_iterator theIterator = mSelectorMap.begin();
		std::advance(theIterator, inItemIndex);
		ThrowIf(theIterator == mSelectorMap.end(), CAException(kAudioHardwareIllegalOperationError), "ZKMORHP_SelectorControl::GetItemKindByIndex: index out of range");
		theAnswer = theIterator->second.mItemKind;
	}
	
	return theAnswer;
}

void	ZKMORHP_SelectorControl::CacheCurrentItemID()
{
	//	Set mCurrentItemID to the value of the hardware. We do nothing here because there is no hardware.
}

//==================================================================================================
//	ZKMORHP_StereoPanControl
//==================================================================================================

ZKMORHP_StereoPanControl::ZKMORHP_StereoPanControl(AudioObjectID inObjectID, AudioClassID inClassID, AudioObjectPropertyScope inDevicePropertyScope, AudioObjectPropertyElement inDevicePropertyElement, UInt32 inLeftChannel, UInt32 inRightChannel, ZKMORHP_PlugIn* inPlugIn, ZKMORHP_Device* inOwningDevice)
:
	HP_StereoPanControl(inObjectID, inClassID, inPlugIn, inOwningDevice),
	mDevicePropertyScope(inDevicePropertyScope),
	mDevicePropertyElement(inDevicePropertyElement),
	mLeftChannel(inLeftChannel),
	mRightChannel(inRightChannel),
	mFullLeftRawValue(0),
	mCenterRawValue(0),
	mFullRightRawValue(0),
	mCurrentRawValue(0)
{
}

ZKMORHP_StereoPanControl::~ZKMORHP_StereoPanControl()
{
}

void	ZKMORHP_StereoPanControl::Initialize()
{
	//	cache the info about the control
	mFullLeftRawValue = 0;
	mCenterRawValue = 512;
	mFullRightRawValue = 1024;
	
	//	set the value to center, since we don't have any hardware
	mCurrentRawValue = mCenterRawValue;
	
	//	cache the current raw value
	CacheRawValue();
}

void	ZKMORHP_StereoPanControl::Teardown()
{
}

AudioObjectPropertyScope	ZKMORHP_StereoPanControl::GetPropertyScope() const
{
	return mDevicePropertyScope;
}

AudioObjectPropertyElement	ZKMORHP_StereoPanControl::GetPropertyElement() const
{
	return mDevicePropertyElement;
}

Float32	ZKMORHP_StereoPanControl::GetValue() const
{
	Float32	theAnswer = 0.0;
	SInt32	theRawValue = GetRawValue();
	Float32	theSpan;
	
	if(theRawValue == mCenterRawValue)
	{
		theAnswer = 0.5;
	}
	else if(theRawValue > mCenterRawValue)
	{
		theSpan = mFullRightRawValue - mCenterRawValue;
		theAnswer = theRawValue - mCenterRawValue;
		theAnswer *= 0.5;
		theAnswer /= theSpan;
		theAnswer += 0.5;
	}
	else
	{
		theSpan = mCenterRawValue - mFullLeftRawValue;
		theAnswer = theRawValue - mFullLeftRawValue;
		theAnswer *= 0.5;
		theAnswer /= theSpan;
	}
	
	return theAnswer;
}

void	ZKMORHP_StereoPanControl::SetValue(Float32 inValue)
{
	SInt32 theRawValue = 0;
	Float32 theSpan;
	
	if(inValue == 0.5)
	{
		theRawValue = mCenterRawValue;
	}
	else if(inValue > 0.5)
	{
		theSpan = mFullRightRawValue - mCenterRawValue;
		inValue -= 0.5;
		inValue *= theSpan;
		inValue *= 2.0;
		theRawValue = static_cast<SInt32>(inValue);
		theRawValue += mCenterRawValue;
	}
	else
	{
		theSpan = mCenterRawValue - mFullLeftRawValue;
		inValue *= theSpan;
		inValue *= 2.0;
		theRawValue = static_cast<SInt32>(inValue);
	}
	
	SetRawValue(theRawValue);
}

void	ZKMORHP_StereoPanControl::GetChannels(UInt32& outLeftChannel, UInt32& outRightChannel) const
{
	outLeftChannel = mLeftChannel;
	outRightChannel = mRightChannel;
}

SInt32	ZKMORHP_StereoPanControl::GetRawValue() const
{
	//	Always get the value from the hardware and cache it in mCurrentRawValue. Note that if
	//	getting the value from the hardware fails for any reason, we just return mCurrentRawValue.
	//	We always just return mCurrentRawValue here because there is no hardware to talk to.
	return mCurrentRawValue;
}

void	ZKMORHP_StereoPanControl::SetRawValue(SInt32 inValue)
{
	//	Set the value in hardware. Note that mCurrentRawValue should be updated only if setting the
	//	hardware value is synchronous. Otherwise, mCurrentRawValue will be updated when the hardware
	//	notifies us that the value of the control changed. Here, we just directly set
	//	mCurrentRawValue because there is no hardware.
	if(inValue != mCurrentRawValue)
	{
		mCurrentRawValue = inValue;
	
		//	we also have to send the change notification
		ValueChanged();
	}
}

void	ZKMORHP_StereoPanControl::CacheRawValue()
{
	//	Set mCurrentRawValue to the value of the hardware. We do nothing here because there is no
	//	hardware.
}
